Skinned Mesh technique.

upi/throb

Intro.

In modern games (and even demos =) we need to do charter animation. It's not a simple task, because our charters must have many different motions, and consist of many (sometimes thousands) polygons.

Game developers designed many different algorithms:

1. Static Frames. Model animation sampled into a sequence of models - one for each frame of animation. (Quake 1)

2. Part animation. Model breaks apart (legs, arms, ...) and is then animated. (Quake 2, maybe 3)

3. Skin. We define a skeleton and our polygon model is the "skin" for it, so we need only to animate skeleton - the skin is animated with it (like in nature).

First method is not so good, because complex models with many polygons take a lot of memory. For example: A 50 frame model with 500 faces and 300 vertices takes 50*(500*6 + 300*12) = 330000 bytes!! Only 50 frames of a cheap model take 330kBytes!! That sux.

The second one is good, but too complex for complex charters (like humans), and needs a lot of space for storing animation keys.

The third method is perfect for human models, animals, tentacles, for all that have skin and bones, but I don't know how it can be done for a kind of Battle Tech robot, imho it's possible.

Implementation.

Skinned Mesh is easy to implement. First of all we define bones, that form our skeleton, assign to each bones a unique number and organize a kind of data base that holds the bones data (transform, animation, hierarchy).

Second, we assign to each vertex in our model, bones that influence it, and for each bone, that affects a vertex, its weight. The weigth of a bone is "how much this bone affects the vertex" and its number is from 0.0 to 1.0, negative numbers possible but imho not needs.

Then, in the animation pipeline of our engine, we insert a simple routine that, using our vertex-bone data, modifes the "original model" into animation frame.

Routine in pseudo C:

for each vertex{
	zeroing temp;
	for each bone { //organize loop through all affecting bones
		multiplies current bone transform Matrix by affection vertex;
		multiplies result by influence of current bone;
		adds result to temp;
	}
	puts temp to our output model data;
}

and in C this looks like:

SkinDatat *SkinData = obj->SkinData;

for(i=0;i<obj->NumVerts;i++){
	skinedPoint.x = 0;
	skinedPoint.y = 0;
	skinedPoint.z = 0;

	for(j=0;j<SkinData[i].NumBones;j++){
		bNum = SkinData[i].Bones[j].boneNum;
		bInf = SkinData[i].Bones[j].weight;

		if(bInf!=0 && bNum!=-1)
	vVectorTransform(&SkinData[i].vertex,&cScene->objs[bNum]->sMatrix,&tempPoint);

		vVectorMul(&tempPoint,bInf,&tempPoint);
		vVectorAdd(&skinedPoint,&tempPoint,&skinedPoint);
	}

	obj->verts[i] = skinedPoint;
}

Note: This routine must be called after the animation of the skeleton!

Addition Tools.

"That's all so cool, and simple, but I don't understand how can I assign bones to vertices!" says a newbie.

It's a good question! =) You can write your own tool, or use 3D Studio MAX 4.0 or greater. The skin in it is modified by a standard modifer, and you can easily access its vertex-bone data. Just write a simple export plugin!

Here is a simple code that dumps skin data for each skinned object into an ascii text file, it can be inserted in ASCIIEXP.CPP of MAXSDK in the nodeEnum method. =)

#include "iskin.h"

Object *objRef = node->GetObjectRef();
if(objRef->SuperClassID()==GEN_DERIVOB_CLASS_ID){
	IDerivedObject *dObj = (IDerivedObject *)objRef;
	Modifier *oMod = dObj->GetModifier(0);


	if(oMod->ClassID()==SKIN_CLASSID){
		ISkin *oSkin = (ISkin *)oMod->GetInterface(I_SKIN);
		ISkinContextData *sCont = oSkin->GetContextInterface(node);

		char *name = node->GetName();
		FILE *f = fopen(name,"w");

		fprintf(f,"numBones: %d\n",oSkin->GetNumBones());
		fprintf(f,"numVerts: %d\n",sCont->GetNumPoints());

		fprintf(f,"\nVertex data:\n");
	//dump vertex data
		for(i=0;i<sCont->GetNumPoints();i++){
		  fprintf(f,"\tVertex: %d\n",i);
		  fprintf(f,"\tnumVertexBones: %d\n",sCont->GetNumAssignedBones(i));

		  for(j=0;j<sCont->GetNumAssignedBones(i);j++){
			fprintf(f,"\t\tBoneNum: %d\n",sCont->GetAssignedBone(i,j));
			fprintf(f,"\t\tBoneWeight: %f\n",sCont->GetBoneWeight(i,j));
		  }
		}

		fprintf(f,"\nBone data:\n");

	//dump bones data
		for(i=0;i<oSkin->GetNumBones();i++){
			fprintf(f,"\tBone: %d\n",i);

			char *bName = oSkin->GetBoneName(i);
			char dummyChar = 0;

			if(bName==0)		//save fprintf =)
				bName = &dummyChar;

			fprintf(f,"\t\tBoneName: %s\n",bName);
			fprintf(f,"\t\tBoneFlag: %d\n",oSkin->GetBoneProperty(i));
		}

		fclose(f);
	}
}

Note: 3D Studio BoneProperty is not important, it's used for spline bones and so.. And if you export your models to .3DS (or use code from 3ds exporter that converts a 3D Studio texture model into UV) you should't check the "Preserve MAX's Texture Coordinates" checkbox, because it can adds vertices (and faces =) to the model, which makes our vertex-bone data incorrect!!!

Happy coding.

UpI